﻿using Micro.Extensions.Utilities;
using Micro.Srv.LogCenter.Services.Interfaces;
using Microsoft.Extensions.Options;
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace Micro.Srv.LogCenter.Services
{
    public class ChainLogService : IChainLogService
    {
        private readonly ElasticClient _client;
        private readonly IOptions<ElasticSearchOptions> _options;

        public ChainLogService(ElasticClientHelper elasticClientHelper, IOptions<ElasticSearchOptions> options)
        {
            _client = elasticClientHelper.ElasticClient;
            _options = options;
        }

        public async Task<ComResult<ChainLogViewModel[]>> GetChainLogsByChainId(string chainId, string logType, LogLevel? logLevel, DateTime beginTime, DateTime endTime)
        {
            if (string.IsNullOrEmpty(chainId))
                return ComResult.Failed<ChainLogViewModel[]>(400, "ChainId 不能为空");

            var index = ESIndexHelper.GetLogIndex(_options.Value.DefaultLogPrefix, beginTime, endTime);

            var response = await _client.SearchAsync<ChainLogViewModel>(s => s
              .Index(index)
              //.Type("doc")
              .TrackTotalHits(true)
              .From(0)
              .Size(200)
              .Query(q => BuildQueryContainer(chainId, logType, logLevel)));

            if (response.ApiCall.Success)
            {
                var result = response.Hits.Select(s =>
                {
                    s.Source.Id = s.Id;
                    return s.Source;
                }).OrderBy(o => o.LogTime).ToArray();

                var maxTimeSpan = result.Length > 0 ? result.Max(m => m.TimeSpan) : 0;

                var returnVal = BuildTreeData("", "", "", maxTimeSpan, result);
                return ComResult.Success(returnVal);
            }

            response.TryGetServerErrorReason(out string reason);
            return ComResult.Failed<ChainLogViewModel[]>(400, reason);
        }

        private QueryContainer BuildQueryContainer(string chainId, string logType, LogLevel? logLevel)
        {
            var queryCombin = new List<Func<QueryContainerDescriptor<ChainLogViewModel>, QueryContainer>>();

            queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.ChainId).Query(chainId)));

            if (!string.IsNullOrEmpty(logType))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.LogType).Query(logType)));

            if (logLevel.HasValue)
                queryCombin.Add(mt => mt.Term(m => m.Field(t => t.LogLevel).Value(GetLogLevelString(logLevel.Value))));

            return Query<ChainLogViewModel>.Bool(b => b.Must(queryCombin));
        }

        private ChainLogViewModel[] BuildTreeData(string parentTrackId, string applicationId, string index, int maxTimespan, ChainLogViewModel[] messages)
        {
            #region internal method
            bool IsChildForTrace(ChainLogViewModel log)
            {
                if ((log.LogType == "Trace" || log.LogType == "AsyncTrace") && log.ParentTrackId == parentTrackId)
                {
                    return true;
                }

                if (log.LogType != "Trace" && log.LogType != "AsyncTrace" && log.TrackId == parentTrackId)
                {
                    return true;
                }

                return false;
            }
            #endregion

            var children = messages.Where(IsChildForTrace).OrderBy(o => o.LogTime).ToArray();

            if (children.Length == 0) return children;
            else
            {
                for (int i = 0; i < children.Length; i++)
                {
                    var item = children[i];
                    item.Index = index + (i + 1).ToString();
                    item.ProgressTimeSpan = (int)((double)item.TimeSpan / maxTimespan * 100);
                    if (item.LogType == "Trace" || item.LogType == "AsyncTrace")
                        item.Children = BuildTreeData(item.TrackId, item.ApplicationId, item.Index, maxTimespan, messages);
                }
            }

            return children;
        }

        private static string GetLogLevelString(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                    return "trce";
                case LogLevel.Debug:
                    return "dbug";
                case LogLevel.Information:
                    return "info";
                case LogLevel.Warning:
                    return "warn";
                case LogLevel.Error:
                    return "fail";
                case LogLevel.Critical:
                    return "crit";
                default:
                    throw new ArgumentOutOfRangeException(nameof(logLevel));
            }
        }
    }
}
